//
//  main.swift
//  SDemo
//
//  Created by zl on 2019/11/1.
//  Copyright © 2019 youtil. All rights reserved.
//

import Foundation
/**
var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName {
    greeting = "Hello, \(name)"
}
 */
//MARK: 面向协议编程
/**
protocol ExampleProtocol {
    var simpleDescription: String { get }
    mutating func adjust()
}
class SimpleClass: ExampleProtocol {
    var simpleDescription: String = "A very simple class."
    var anotherProperty: Int = 69105
    func adjust() {
        simpleDescription += " Now 100% adjusted."
    }
}
var a = SimpleClass()
a.adjust()
let aDesc = a.simpleDescription
func makeArray<T>(repeating item: T, numberOfTimes: Int) -> [T] {
    var result = [T]()
    for _ in 0..<numberOfTimes {
        result.append(item)
    }
    return result
}
var b = makeArray(repeating: "knock", numberOfTimes: 4)
*/
//MARK: 函数式编程
/**
//1. 一等函数
typealias Distance = Double
struct Position {
    var x: Distance
    var y: Distance
}
extension Position {
    func within(range: Distance) -> Bool {
        return sqrt(x * x + y * y) <= range
    }
}
extension Position {
    func minus(_ p: Position) -> Position {
        return Position(x: x - p.x, y: y - p.y)
    }
    var length: Double {
        return sqrt(x * x + y * y)
    }
}
struct Ship {
    var position: Position
    var firingRange: Distance
    var unsafeRange: Distance
}
extension Ship {
    func canEngage(ship target: Ship) -> Bool {
        let dx = target.position.x - position.x
        let dy = target.position.y - position.y
        let targetDistance = sqrt(dx * dx + dy * dy)
        return targetDistance <= firingRange
    }
}
extension Ship {
    func canSafelyEngage(ship target: Ship, friendly: Ship) -> Bool {
        let targetDistance = target.position.minus(position).length
        let friendlyDistance = friendly.position.minus(target.position).length
        return targetDistance <= firingRange &&
                targetDistance > unsafeRange &&
                friendlyDistance > unsafeRange
    }
}
typealias Region = (Position) -> Bool
func circle(radius: Distance) -> Region {
    return { point in point.length <= radius}
}
func circle2(radius: Distance, center: Position) -> Region {
    return { point in point.minus(center).length <= radius }
}
func shift(_ region: @escaping Region, by offset: Position) -> Region {
    return { point in region(point.minus(offset)) }
}
func invert(_ region: @escaping Region) -> Region {
    return { point in !region(point) }
}
func intersect(_ region: @escaping Region, with other: @escaping Region) -> Region {
    return { point in region(point) && other(point) }
}
func union(_ region: @escaping Region, with other: @escaping Region) -> Region {
    return { point in region(point) || other(point) }
}
func test<T>(_ m: @escaping (T) -> Bool) -> (T) -> Bool {
    return { temp in m(temp) }
}

let cities = ["Paris": 2241, "Madrid": 3165, "Amsterdam": 827, "Berlin": 3562]
if let madridPopulation = cities["Madrid"] {
    print("The population of Madrid is \(madridPopulation * 1000)")
}else{
    print("Unknown city: Madrid")
}

infix operator ??
func ??<T>(optional: T?, defaultValue: @autoclosure () throws -> T) rethrows -> T {
    if let x = optional {
        return x
    } else {
        return try defaultValue()
    }
}
// MARK: 枚举
enum LookupError: Error {
    case capitalNotFound
    case populationNotFound
}
//
struct MySet<Element: Equatable> {
    var storage: [Element] = []
    var isEmpty: Bool {
        return storage.isEmpty
    }
    func contains(_ element: Element) -> Bool {
        return storage.contains(element)
    }
    func inserting(_ x: Element) -> MySet {
        return contains(x) ? self : MySet(storage: storage + [x])
    }
}
indirect enum BinarySearchTree<Element: Comparable> {
    case leaf
    case node(BinarySearchTree<Element>, Element, BinarySearchTree<Element>)
}
extension BinarySearchTree {
    init() {
    self = .leaf
    }
    init(_ value: Element) {
        self = .node(.leaf, value, .leaf)
    }
}
extension BinarySearchTree {
    var count: Int {
        switch self {
            case .leaf:
                return 0
            case let .node(left, _, right):
                return 1 + left.count + right.count
        }
    }
}


extension IteratorProtocol {
    mutating func find(predicate: (Element) -> Bool) -> Element? {
        while let x = next() {
            if predicate(x) {
                return x
            }
        }
        return nil
    }
}
struct ReverseIndexIterator: IteratorProtocol {
    var index: Int
    init<T>(array: [T]) {
        index = array.endIndex - 1
    }
    mutating func next() -> Int? {
        guard index >= 0 else { return nil }
        defer { index -= 1 }
        return index
    }
}
struct PowerIterator: IteratorProtocol {
    var power: NSDecimalNumber = 1
    mutating func next() -> NSDecimalNumber? {
        power = power.multiplying(by: 2)
        return power
    }
}
extension PowerIterator {
    mutating func find(where predicate:(NSDecimalNumber) -> Bool) -> NSDecimalNumber? {
        while let x = next() {
            if predicate(x) {
                return x
            }
        }
        return nil
    }
}
struct FileLinesIterator: IteratorProtocol {
    let lines: [String]
    var currentLine: Int = 0
    init(filename: String) throws {
        let contents: String = try String(contentsOfFile: filename)
        lines = contents.components(separatedBy: .newlines)
    }
    mutating func next() -> String? {
        guard currentLine < lines.endIndex else { return nil }
        defer { currentLine += 1 }
        return lines[currentLine]
    }
}
var a = (1...10).filter { $0 % 3 == 0 }.map { $0 * $0 }
print(a)
let lazyResult = (1...10).lazy.filter { $0 % 3 == 0 }.map { $0 * $0 }
print(Array(lazyResult))
 */
//MARK: POP
/*
protocol Withdrawing {
    func withdraw(amount: Int) -> Bool
}
final class MoneyPile: Withdrawing {
    let value: Int
    var quantity: Int
    var next: Withdrawing?
    init(value: Int, quantity: Int, next: Withdrawing?) {
        self.value = value
        self.quantity = quantity
        self.next = next
    }
    func withdraw(amount: Int) -> Bool {
        var amount = amount
        func canTakeSomeBill(want: Int) -> Bool {
            return (want / self.value) > 0
        }
        var quantity = self.quantity
        while canTakeSomeBill(want: amount) {
            if quantity == 0 {
                break
            }
            amount -= self.value
            quantity -= 1
        }
        guard amount > 0 else {
            return true
        }
        if let next = self.next {
            return next.withdraw(amount: amount)
        }
        return false
    }
}
final class ATM: Withdrawing {
    private var hundred: Withdrawing
    private var fifty: Withdrawing
    private var twenty: Withdrawing
    private var ten: Withdrawing
    
    private var startPile: Withdrawing {
        return self.hundred
    }
    init(hundred: Withdrawing, fifty: Withdrawing, twenty: Withdrawing, ten: Withdrawing) {
        self.hundred = hundred
        self.fifty = fifty
        self.twenty = twenty
        self.ten = ten
    }
    func withdraw(amount: Int) -> Bool {
        return startPile.withdraw(amount: amount)
    }
}
let ten = MoneyPile(value: 10, quantity: 6, next: nil)
let twenty = MoneyPile(value: 20, quantity: 2, next: ten)
let fifty = MoneyPile(value: 50, quantity: 2, next: twenty)
let hundred = MoneyPile(value: 100, quantity: 1, next: fifty)

var atm = ATM(hundred: hundred, fifty: fifty, twenty: twenty, ten: ten)
var a = atm.withdraw(amount: 310)
var b = atm.withdraw(amount: 100)
print(a, b)

protocol DoorCommand { func execute() -> String }
final class OpenCommand: DoorCommand {
    let doors: String
    required init(doors: String) {
        self.doors = doors
    }
    func execute() -> String {
        return "Opened \(doors)"
    }
}
final class CloseCommand: DoorCommand {
    let doors: String
    required init(doors: String) {
        self.doors = doors
    }
    func execute() -> String {
        return "Closed \(doors) "
    }
}
final class HAL9000DoorsOperations {
    let openCommand: DoorCommand
    let closeCommand: DoorCommand
    init(doors: String) {
        self.openCommand = OpenCommand(doors: doors)
        self.closeCommand = CloseCommand(doors: doors)
    }
    func close() -> String {
        return closeCommand.execute()
    }
    func open() -> String {
        return openCommand.execute()
    }
}

/** defer */
func testFunc() {
    defer {
        defer {
            print("defer in")
        }
        print("defer1")
    }
    defer {
        print("defer 2")
    }
    print("end")
}
testFunc()
 */

// MARK: Swift Algorithms
//import Algorithms
// chunks
//let numbers = [10, 20, 30, 10, 40, 40 , 10, 20]
//let chunks = numbers.chunked(by: { $0 <= $1 })
//print(chunks)
//let names = ["Cassie", "Chloe", "Jasmine", "Jordan", "Taylor"]
//let chunks2 = names.chunked(on: \.first)
//print(chunks2)

// combinations
//let numbers2 = [10, 20, 30, 40]
//for combo in numbers2.combinations(ofCount: 3) {
//    print(combo)
//}

// Chain
//let numbers = chain([10, 20, 30], 1...5)
//print(Array(numbers))
//let letters = chain("abcde", "FGHIJ")
//print(String(letters))

// Cycle
//for (odd, n) in zip([true, false].cycled(), 1...10) {
//    print(odd)
//    print(n)
//}
//for x in (1...3).cycled(times: 3) {
//    print(x)
//}

// Indexed
//let numbers = [10, 20, 30, 40, 50]
//var matchingIndices: Set<Int> = []
//for (i, n) in numbers.indexed() {
//    if n.isMultiple(of: 20) {
//        matchingIndices.insert(i)
//    }
//}
//print(matchingIndices)

// Intersperse
// Partition
//var numbers = [20, 40, 80, 10, 50, 60, 30, 70]
//let p1 = numbers.partition(by: { $0.isMultiple(of: 20) })
//print(p1)
//print(numbers)
//numbers = [20, 40, 80, 10, 50, 60, 30, 70]
//let p2 = numbers.stablePartition(by: { $0.isMultiple(of: 20) })
//print(p2)
//print(numbers)
// Permutations
//let seasons = ["winter", "spring", "summer", "fall"]
//for (year, season) in product(2018...2020, seasons) {
//    print(year, season)
//}
//var cats = ["Tobby", "Sbiw", "Cycloman"]
//var masters = ["Joson", "Curly", "Lana"]
//var names = [cats, masters]
////let catsWithHat = cats.map { $0 + " wear hat" }
//func wearHat(name: String) -> String {
//    return name + " wear hat"
//}
//let allWearHat = names.flatMap { $0.map(wearHat(name:))}
//print(allWearHat)

// protocol
